package org.jsola.hr.service.excel;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.fastjson.JSON;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.jsola.common.BeanKit;
import org.jsola.common.DateKit8;
import org.jsola.exception.ParamException;
import org.jsola.hr.common.DateUtils;
import org.jsola.hr.constant.EmpSocStatus;
import org.jsola.hr.dto.excel.EmpChangeImportDTO;
import org.jsola.hr.entity.EmpInfoDO;
import org.jsola.hr.entity.EmpSocPlanDO;
import org.jsola.hr.entity.EmpSocRelatedDO;
import org.jsola.hr.service.IEmpChangesService;
import org.jsola.hr.service.IEmpInfoService;
import org.jsola.hr.service.IEmpSocRelatedService;
import org.jsola.hr.service.impl.EmpSocPlanServiceImpl;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author lyh
 */
@Slf4j
@Data
@EqualsAndHashCode(callSuper = true)
public class EmpChangeImportListener extends BaseAnalysisEventListener<EmpChangeImportDTO> {

    /**
     * 公司Id
     */
    private String companyId;

    /**
     * 达到最大值后入库
     */
    private static final Integer MAX_COUNT = 2000;

    /**
     * 在职状态枚举
     */
    private static final Integer INCUMBENCY_STATUS = 2;

    /**
     * 待离职状态枚举
     */
    private static final Integer TO_LEAVE_STATUS = 3;

    /**
     * 错误列常量 (身份证号)
     */
    private static final String ID_CARD = "身份证号:";

    /**
     * 错误列常量 (离职原因)
     */
    private static final String LEAVE_OFFICE_REASON = "离职原因:";

    /**
     * 错误列常量 (离职日期)
     */
    private static final String LEAVE_OFFICE_DATE = "离职日期:";

    /**
     * 错误列常量 (社保减员年月)
     */
    private static final String STOP_SOC_DATE = "社保减员年月:";

    /**
     * 错误列常量 (医保减员年月)
     */
    private static final String STOP_ME_DATE = "医保减员年月:";

    /**
     * 错误列常量 (公积金减员年月)
     */
    private static final String SEALED_DATE = "公积金减员年月:";

    /**
     * 服务费
     */
    private static final String SERVICE_COST = "服务费:";

    /**
     * 没有错误的列表
     */
    private List<EmpChangeImportDTO> successList = new ArrayList<>();

    /**
     * 有错误的列表
     */
    private List<EmpChangeImportDTO> errorList = new ArrayList<>();

    /**
     * 重复的身份证号
     */
    private List<String> repeatIdCard = new ArrayList<>();

    /**
     * 身份证号列表
     */
    private List<String> allIdCard = new ArrayList<>();

    /**
     * 社保子项
     */
    private static final Integer SOC_ITEM = 1;

    /**
     * 医保子项
     */
    private static final Integer ME_ITEM = 3;

    /**
     * 公积金子项
     */
    private static final Integer PF_ITEM = 2;

    /**
     * 校验日期字符串字符数
     */
    private static final Integer SIX = 6;

    /**
     * 最大月份
     */
    private static final Integer MAX_MONTH = 12;

    /**
     * 格式化日期长度
     */
    private static final Integer DATE_FORMAT_LEN = 3;

    /**
     * 10
     */
    private static final Integer TEN = 10;

    /**
     * 缓存要入库的数据
     */
    protected List<EmpChangeImportDTO> dataList = new ArrayList<>();

    @Override
    public void invoke(EmpChangeImportDTO data, AnalysisContext context) {
        // 获取行的索引
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        // 这里直接往 List 里面放 如果在这里做校验每次都要执行查询, 如果数据量太大会执行的很慢
        // 所以把数据全汇总到 saveData 里再做校验
        // 是否减员  如果excel里面没填不走转换器  不知道为啥  所以这里手动转换
        if (ObjectUtils.isEmpty(data.getWhetherSocDecrease())) {
            data.setWhetherSocDecrease(false);
        }
        if (ObjectUtils.isEmpty(data.getWhetherMeDecrease())) {
            data.setWhetherMeDecrease(false);
        }
        if (ObjectUtils.isEmpty(data.getWhetherSealed())) {
            data.setWhetherSealed(false);
        }
        if (allIdCard.contains(data.getIdCard())) {
            repeatIdCard.add(data.getIdCard());
        } else {
            allIdCard.add(data.getIdCard());
        }
        dataList.add(data);
        if (dataList.size() >= MAX_COUNT) {
            // 入库
            saveData();
            // 存储完成清理 list
            dataList.clear();
        }
    }

    @Override
    public void saveData() {
        log.info("{}条数据，开始存储数据库！", dataList.size());
        ParamException.notEmpty(dataList, "没有数据需要入库");
        ParamException.notNull(tokenUser, "没有设置当前登录用户");
        // 把导入的身份证号拿到
        List<String> importIdCardList = dataList.stream()
                .map(EmpChangeImportDTO::getIdCard).collect(Collectors.toList());
        importIdCardList.removeIf(Objects::isNull);
        IEmpInfoService empInfoService = BeanKit.getBean(IEmpInfoService.class);
        IEmpSocRelatedService empSocRelatedService = BeanKit.getBean(IEmpSocRelatedService.class);
        // 根据导入的身份证号查询在职员工,下面这个方法查询的是所有员工,再过滤一下在职/待离职员工,empInfo类太大了进去就卡,懒得加新方法了
        List<EmpInfoDO> empInfoList = empInfoService.selectEmpByIdCardList(companyId, importIdCardList, tokenUser);
        List<EmpInfoDO> empInfoListFilter = empInfoList.stream().
                filter(empInfo -> INCUMBENCY_STATUS.equals(empInfo.getStatus()) ||
                        TO_LEAVE_STATUS.equals(empInfo.getStatus())).collect(Collectors.toList());
        Map<String, EmpInfoDO> idCardEmpMap = new HashMap<>(128);
        Map<String, EmpSocRelatedDO> empSocRelatedMap = new HashMap<>(128);
        if (!CollectionUtils.isEmpty(empInfoListFilter)) {
            idCardEmpMap = empInfoListFilter.stream().collect(Collectors.toMap(EmpInfoDO::getIdCard, v -> v));
            List<String> empIdList = empInfoListFilter.stream().map(EmpInfoDO::getId).collect(Collectors.toList());
            List<EmpSocRelatedDO> empSocRelatedList = empSocRelatedService.selectByEmpIdList(empIdList, tokenUser.getSiteId());
            if (!CollectionUtils.isEmpty(empSocRelatedList)) {
                empSocRelatedMap = empSocRelatedList.stream().collect(Collectors.toMap(EmpSocRelatedDO::getEmpId, v -> v));
            }
        }
        // 错误校验
        checkError(dataList, idCardEmpMap, empSocRelatedMap);
        IEmpChangesService empChangesService = BeanKit.getBean(IEmpChangesService.class);
        empChangesService.confirmLeaveOfficeImportSaveDate(successList, companyId, idCardEmpMap, tokenUser);
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        // 验证EXCEL表头对应名称
        List<String> stringList = new ArrayList<>();
        stringList.add("姓名");
        stringList.add("身份证号");
        stringList.add("离职原因");
        stringList.add("离职日期");
        stringList.add("离职备注");
        stringList.add("是否社保减员");
        stringList.add("社保减员年月");
        stringList.add("是否医保减员");
        stringList.add("医保减员年月");
        stringList.add("是否公积金减员");
        stringList.add("公积金减员年月");
        stringList.add("服务费");
        if (headMap.size() != stringList.size()) {
            throw new ParamException("导入的员工信息模板不正确（请查看模板表头）");
        }
        for (Map.Entry<Integer, String> entry : headMap.entrySet()) {
            if (!stringList.contains(entry.getValue())) {
                throw new ParamException("导入的员工信息模板不正确（请查看模板表头）");
            }
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据，确保最后遗留的数据也存储到数据库
        // 但是只解析的话不需要入库
        if (!parseOnly) {
            saveData();
        }
    }

    private void checkError(List<EmpChangeImportDTO> dataList, Map<String, EmpInfoDO> idCardEmpMap,
                            Map<String, EmpSocRelatedDO> empSocRelatedMap) {
        EmpSocPlanServiceImpl empSocPlanService = BeanKit.getBean(EmpSocPlanServiceImpl.class);
        for (EmpChangeImportDTO empChangeImport : dataList) {
            EmpInfoDO empInfoDO = idCardEmpMap.get(empChangeImport.getIdCard());
            // haveError 判断这条数据有没有错误 最后影响把这条数据放到那个 List 里
            boolean haveError = false;
            Set<String> errorColumn = new HashSet<>();
            StringBuilder errorInfo = new StringBuilder();
            // 身份证一堆错误校验
            if (StringUtils.isEmpty(empChangeImport.getIdCard())) {
                // 如果身份证号为空  后面肯定啥也查不到  所以直接不用校验了
                errorInfo.append(ID_CARD + "为空;");
                errorColumn.add("idCard");
                haveError = true;
            } else {
                if (repeatIdCard.contains(empChangeImport.getIdCard())) {
                    errorInfo.append(ID_CARD + "数据重复，请检查;");
                    errorColumn.add("idCard");
                    haveError = true;
                }
                if (ObjectUtils.isEmpty(empInfoDO)) {
                    errorInfo.append(ID_CARD + "未匹配到在职员工，请验证后重新导入;");
                    errorColumn.add("idCard");
                    haveError = true;
                    // 如果没有匹配到员工 后面也可以直接不用校验了
                } else {
                    // 判断是否在待离职列表
                    if (empInfoDO.getChangeStatus() == 3) {
                        errorInfo.append("该员工已存在待离职列表，请前往待离职管理确认离职;");
                        haveError = true;
                    }
                    // 离职日期  一堆错
                    if (ObjectUtils.isEmpty(empChangeImport.getLeaveOfficeDate())) {
                        errorInfo.append(LEAVE_OFFICE_DATE + "为空;");
                        errorColumn.add("leaveOfficeDate");
                        haveError = true;
                    } else {
                        if (checkDateFormat(empChangeImport)) {
                            errorInfo.append(LEAVE_OFFICE_DATE + "格式有误;");
                            errorColumn.add("leaveOfficeDate");
                            haveError = true;
                        } else {
                            Date leaveOfficeDate = DateKit8.parseDate(empChangeImport.getLeaveOfficeDate());
                            Date joinDate = empInfoDO.getJoinDate();
                            // 判断入职日期
                            if (!ObjectUtils.isEmpty(joinDate) && leaveOfficeDate.before(joinDate)) {
                                errorInfo.append(LEAVE_OFFICE_DATE + "员工离职日期不能小于入职日期;");
                                errorColumn.add("leaveOfficeDate");
                                haveError = true;
                            }
                        }
                    }
                    EmpSocRelatedDO empSocRelatedDO = empSocRelatedMap.get(empInfoDO.getId());
                    // 是否社保减员 一堆错误
                    if (empChangeImport.getWhetherSocDecrease()) {
                        if (checkStatus(empSocRelatedDO, SOC_ITEM)) {
                            errorInfo.append("员工未参社保，无法进行社保减员;");
                            errorColumn.add("whetherSocDecrease");
                            haveError = true;
                        } else if (StringUtils.isEmpty(empChangeImport.getStopSocDate())) {
                            errorInfo.append(STOP_SOC_DATE + "您选择了社保减员，请填写停保年月;");
                            errorColumn.add("stopSocDate");
                            haveError = true;
                        } else if (checkDateStr(empChangeImport.getStopSocDate())) {
                            errorInfo.append(STOP_SOC_DATE + "格式有误，请参考标准模板批注;");
                            errorColumn.add("stopSocDate");
                            haveError = true;
                        } else if (checkDateGreaterThanCurrent(empChangeImport.getStopSocDate())) {
                            errorInfo.append(STOP_SOC_DATE + "减员年月只能是当月及其未来月;");
                            errorColumn.add("stopSocDate");
                            haveError = true;
                        } else {
                            List<EmpSocPlanDO> empSocPlanList = empSocPlanService
                                    .selectPlanByEmpId(empInfoDO.getId(), SOC_ITEM, tokenUser);
                            if (!CollectionUtils.isEmpty(empSocPlanList)) {
                                errorInfo.append(STOP_SOC_DATE + "该员工已存在减员计划;");
                                errorColumn.add("stopSocDate");
                                haveError = true;
                            }
                        }
                    }
                    // 是否医保减员 一堆错误
                    if (empChangeImport.getWhetherMeDecrease()) {
                        if (checkStatus(empSocRelatedDO, ME_ITEM)) {
                            errorInfo.append("员工未参医保，无法进行医保减员;");
                            errorColumn.add("whetherMeDecrease");
                            haveError = true;
                        } else if (StringUtils.isEmpty(empChangeImport.getStopMeDate())) {
                            errorInfo.append(STOP_ME_DATE + "您选择了医保减员，请填写停保年月;");
                            errorColumn.add("stopMeDate");
                            haveError = true;
                        } else if (checkDateStr(empChangeImport.getStopMeDate())) {
                            errorInfo.append(STOP_ME_DATE + "格式有误，请参考标准模板批注;");
                            errorColumn.add("stopMeDate");
                            haveError = true;
                        } else if (checkDateGreaterThanCurrent(empChangeImport.getStopMeDate())) {
                            errorInfo.append(STOP_ME_DATE + "减员年月只能是当月及其未来月;");
                            errorColumn.add("stopMeDate");
                            haveError = true;
                        } else {
                            List<EmpSocPlanDO> empSocPlanList = empSocPlanService
                                    .selectPlanByEmpId(empInfoDO.getId(), ME_ITEM, tokenUser);
                            if (!CollectionUtils.isEmpty(empSocPlanList)) {
                                errorInfo.append(STOP_ME_DATE + "该员工已存在减员计划;");
                                errorColumn.add("stopMeDate");
                                haveError = true;
                            }
                        }
                    }
                    // 是否公积金减员 一个错误
                    if (empChangeImport.getWhetherSealed()) {
                        if (checkStatus(empSocRelatedDO, PF_ITEM)) {
                            errorInfo.append("员工未参公积金，无法进行公积金减员;");
                            errorColumn.add("whetherSealed");
                            haveError = true;
                        } else if (StringUtils.isEmpty(empChangeImport.getSealedDate())) {
                            errorInfo.append(SEALED_DATE + "您选择了公积金减员，请填写停保年月;");
                            errorColumn.add("sealedDate");
                            haveError = true;
                        } else if (checkDateStr(empChangeImport.getSealedDate())) {
                            errorInfo.append(SEALED_DATE + "格式有误，请参考标准模板批注;");
                            errorColumn.add("sealedDate");
                            haveError = true;
                        } else if (checkDateGreaterThanCurrent(empChangeImport.getSealedDate())) {
                            errorInfo.append(SEALED_DATE + "减员年月只能是当月及其未来月;");
                            errorColumn.add("sealedDate");
                            haveError = true;
                        } else {
                            List<EmpSocPlanDO> empSocPlanList = empSocPlanService
                                    .selectPlanByEmpId(empInfoDO.getId(), PF_ITEM, tokenUser);
                            if (!CollectionUtils.isEmpty(empSocPlanList)) {
                                errorInfo.append(SEALED_DATE + "该员工已存在减员计划;");
                                errorColumn.add("sealedDate");
                                haveError = true;
                            }
                        }
                    }
                    if (!StringUtils.isEmpty(empChangeImport.getServiceCost())) {
                        try {
                            BigDecimal service = new BigDecimal(empChangeImport.getServiceCost());
                            if (service.compareTo(BigDecimal.ZERO) < 0) {
                                haveError = true;
                                errorColumn.add("serviceCost");
                                errorInfo.append(SERVICE_COST + "服务费不能为负数");
                            } else {
                                // 四舍五入保留两位小数
                                empChangeImport.setServiceCost(service
                                        .setScale(2, RoundingMode.HALF_UP).toPlainString());
                            }
                        } catch (Exception e) {
                            haveError = true;
                            errorColumn.add("serviceCost");
                            errorInfo.append(SERVICE_COST + "服务费格式错误");
                        }
                    }
                }
            }
            // 离职原因一个错误
            if (ObjectUtils.isEmpty(empChangeImport.getLeaveOfficeReason())) {
                errorInfo.append(LEAVE_OFFICE_REASON + "为空;");
                errorColumn.add("leaveOfficeReason");
                haveError = true;
            }
            empChangeImport.setErrorColumn(errorColumn);
            empChangeImport.setErrorInfo(errorInfo.toString());
            if (haveError) {
                errorList.add(empChangeImport);
            } else {
                successList.add(empChangeImport);
            }
        }
    }

    /**
     * true 返回错误
     */
    private Boolean checkStatus(EmpSocRelatedDO empSocRelatedDO, Integer checkType) {
        if (checkType.equals(SOC_ITEM)) {
            return !EmpSocStatus.INCREMENT.getValue().equals(empSocRelatedDO.getSocState()) &&
                    !EmpSocStatus.NORMAL_SOC.getValue().equals(empSocRelatedDO.getSocState());
        } else if (checkType.equals(ME_ITEM)) {
            return !EmpSocStatus.INCREMENT.getValue().equals(empSocRelatedDO.getMeState()) &&
                    !EmpSocStatus.NORMAL_SOC.getValue().equals(empSocRelatedDO.getMeState());
        } else {
            return !EmpSocStatus.INCREMENT.getValue().equals(empSocRelatedDO.getPfState()) &&
                    !EmpSocStatus.NORMAL_SOC.getValue().equals(empSocRelatedDO.getPfState());
        }
    }

    /**
     * true 格式错误
     */
    private Boolean checkDateStr(String dateStr) {
        if (!SIX.equals(dateStr.length())) {
            return true;
        }
        String year = dateStr.substring(0, 4);
        String month = dateStr.substring(5);
        try {
            Integer.parseInt(year);
            int monthInt = Integer.parseInt(month);
            if (monthInt > MAX_MONTH) {
                return true;
            }
        } catch (Exception e) {
            return true;
        }
        return false;
    }

    /**
     * true 格式错误
     */
    private Boolean checkDateFormat(EmpChangeImportDTO empChangeImport) {
        String formatDate;
        try {
            String date = empChangeImport.getLeaveOfficeDate();
            String replaceDate = date.replaceAll("/", "-");
            String[] replaceDateSplit = replaceDate.split("-");
            if (replaceDateSplit.length == DATE_FORMAT_LEN) {
                formatDate = replaceDateSplit[0] + "-" + checkNumLessThanZero(replaceDateSplit[1]) + "-"
                        + checkNumLessThanZero(replaceDateSplit[2]);
                DateKit8.parseDate(formatDate);
            } else {
                return true;
            }
        } catch (Exception e) {
            return true;
        }
        if (!StringUtils.isEmpty(formatDate)) {
            empChangeImport.setLeaveOfficeDate(formatDate);
        }
        return false;
    }

    /**
     * 校验月和日是否小于10小于前面填0
     */
    private String checkNumLessThanZero(String num) {
        if (Integer.parseInt(num) < TEN && num.length() == 1) {
            num = 0 + num;
        }
        return num;
    }

    /**
     * 判断excel键入的时间是否小于当前时间
     */
    private Boolean checkDateGreaterThanCurrent(String date) {
        // excel键入的时间
        int scannerDate = Integer.parseInt(date);
        // 当前时间
        int currentDate = Integer.parseInt(DateUtils.getYearMonthStr(new Date()));
        return scannerDate < currentDate;
    }
}
